#include "stdafx.h"
#include "net/HTTPClientSession.hpp"
#include "net/HTTPStream.hpp"
#include "net/HTTPHeaderStream.hpp"
#include "net/HTTPChunkedStream.hpp"
#include "net/HTTPFixedLengthStream.hpp"
#include "net/HTTPBasicCredentials.hpp"
#include "charset/RegularExpression.hpp"
using JHCPP::charset::CRegularExpression;
#include "utils/numberformatter.hpp"
using JHCPP::utils::CNumberFormatter;
#include "stream/streambuf.hpp"
using JHCPP::stream::CCountingOutputStream;

NET_NAMESPACE_BEGIN

CHTTPClientSession::ProxyConfig CHTTPClientSession::m_globalProxyConfig;

CHTTPClientSession::CHTTPClientSession()
	: m_port(CHTTPSession::HTTP_PORT),
	m_proxyConfig(m_globalProxyConfig),
	m_keepAliveTimeout(DEFAULT_KEEP_ALIVE_TIMEOUT, 0),
	m_reconnect(false),
	m_mustReconnect(false),
	m_expectResponseBody(false)
{
}

CHTTPClientSession::CHTTPClientSession(const CStreamSocket& socket)
	: CHTTPSession(socket),
	m_port(CHTTPSession::HTTP_PORT),
	m_proxyConfig(m_globalProxyConfig),
	m_keepAliveTimeout(DEFAULT_KEEP_ALIVE_TIMEOUT, 0),
	m_reconnect(false),
	m_mustReconnect(false),
	m_expectResponseBody(false)
{
}

CHTTPClientSession::CHTTPClientSession(const CSocketAddress& address)
	: m_host(address.host().toString()),
	m_port(address.port()),
	m_proxyConfig(m_globalProxyConfig),
	m_keepAliveTimeout(DEFAULT_KEEP_ALIVE_TIMEOUT, 0),
	m_reconnect(false),
	m_mustReconnect(false),
	m_expectResponseBody(false)
{
}

CHTTPClientSession::CHTTPClientSession(const std::string& host, UInt16 port)
	: m_host(host),
	m_port(port),
	m_proxyConfig(m_globalProxyConfig),
	m_keepAliveTimeout(DEFAULT_KEEP_ALIVE_TIMEOUT, 0),
	m_reconnect(false),
	m_mustReconnect(false),
	m_expectResponseBody(false)
{
}

CHTTPClientSession::CHTTPClientSession(const std::string& host, UInt16 port, const ProxyConfig& proxyConfig)
	: m_host(host),
	m_port(port),
	m_proxyConfig(proxyConfig),
	m_keepAliveTimeout(DEFAULT_KEEP_ALIVE_TIMEOUT, 0),
	m_reconnect(false),
	m_mustReconnect(false),
	m_expectResponseBody(false)
{
}

CHTTPClientSession::~CHTTPClientSession()
{
}

void CHTTPClientSession::setHost(const std::string& host)
{
	if (!connected())
		m_host = host;
	else
		throw IllegalStateException("Cannot set the host for an already connected session");
}

const std::string& CHTTPClientSession::getHost() const
{
	return m_host;
}

void CHTTPClientSession::setPort(UInt16 port)
{
	if (!connected())
		m_port = port;
	else
		throw IllegalStateException("Cannot set the port number for an already connected session");
}

UInt16 CHTTPClientSession::getPort() const
{
	return m_port;
}

void CHTTPClientSession::setProxy(const std::string& host, UInt16 port)
{
	if (!connected())
	{
		m_proxyConfig.host = host;
		m_proxyConfig.port = port;
	}
	else 
		throw IllegalStateException("Cannot set the proxy host and port for an already connected session");
}

void CHTTPClientSession::setProxyHost(const std::string& host)
{
	if (!connected())
		m_proxyConfig.host = host;
	else
		throw IllegalStateException("Cannot set the proxy host for an already connected session");
}

const std::string& CHTTPClientSession::getProxyHost() const
{
	return m_proxyConfig.host;
}

void CHTTPClientSession::setProxyPort(UInt16 port)
{
	if (!connected())
		m_proxyConfig.port = port;
	else
		throw IllegalStateException("Cannot set the proxy port number for an already connected session");
}

UInt16 CHTTPClientSession::getProxyPort() const
{
	return m_proxyConfig.port;
}

void CHTTPClientSession::setProxyCredentials(const std::string& username, const std::string& password)
{
	m_proxyConfig.username = username;
	m_proxyConfig.password = password;
}

void CHTTPClientSession::setProxyUsername(const std::string& username)
{
	m_proxyConfig.username = username;
}

const std::string& CHTTPClientSession::getProxyUsername() const
{
	return m_proxyConfig.username;
}

void CHTTPClientSession::setProxyPassword(const std::string& password)
{
	m_proxyConfig.password = password;
}

const std::string& CHTTPClientSession::getProxyPassword() const
{
	return m_proxyConfig.password;
}

void CHTTPClientSession::setProxyConfig(const ProxyConfig& config)
{
	m_proxyConfig = config;
}

const CHTTPClientSession::ProxyConfig& CHTTPClientSession::getProxyConfig() const
{
	return m_proxyConfig;
}


void CHTTPClientSession::setGlobalProxyConfig(const ProxyConfig& config)
{
	m_globalProxyConfig = config;
}

const CHTTPClientSession::ProxyConfig& CHTTPClientSession::getGlobalProxyConfig()
{
	return m_globalProxyConfig;
}

void CHTTPClientSession::setKeepAliveTimeout(const CTimeSpan& timeout)
{
	m_keepAliveTimeout = timeout;
}

const CTimeSpan& CHTTPClientSession::getKeepAliveTimeout() const
{
	return m_keepAliveTimeout;
}

std::ostream& CHTTPClientSession::sendRequest(CHTTPRequest& request)
{
	clearException();
	m_pResponseStream = 0;

	bool keepAlive = getKeepAlive();
	if ((connected() && !keepAlive) || mustReconnect())
	{
		close();
		m_mustReconnect = false;
	}
	try
	{
		if (!connected())
			reconnect();
		if (!keepAlive)
			request.setKeepAlive(false);
		if (!request.has(CHTTPRequest::HOST))
			request.setHost(m_host, m_port);
		if (!m_proxyConfig.host.empty() && !bypassProxy())
		{
			request.setURI(proxyRequestPrefix() + request.getURI());
			proxyAuthenticate(request);
		}
		m_reconnect = keepAlive;
		m_expectResponseBody = request.getMethod() != CHTTPRequest::HTTP_HEAD;
		if (request.getChunkedTransferEncoding())
		{
			CHTTPHeaderOutputStream hos(*this);
			request.write(hos);
			m_pRequestStream = new CHTTPChunkedOutputStream(*this);
		}
		else if (request.hasContentLength())
		{
			CCountingOutputStream cs;
			request.write(cs);
		#if JH_HAVE_INT64
			m_pRequestStream = new CHTTPFixedLengthOutputStream(*this, request.getContentLength64() + cs.chars());
		#else
			m_pRequestStream = new CHTTPFixedLengthOutputStream(*this, request.getContentLength() + cs.chars());
		#endif
			request.write(*m_pRequestStream);
		}
		else if ((request.getMethod() != CHTTPRequest::HTTP_PUT && request.getMethod() != CHTTPRequest::HTTP_POST) || request.has(CHTTPRequest::UPGRADE))
		{
			CCountingOutputStream cs;
			request.write(cs);
			m_pRequestStream = new CHTTPFixedLengthOutputStream(*this, cs.chars());
			request.write(*m_pRequestStream);
		}
		else
		{
			m_pRequestStream = new CHTTPOutputStream(*this);
			request.write(*m_pRequestStream);
		}	
		m_lastRequest.update();
		return *m_pRequestStream;
	}
	catch (CException&)
	{
		close();
		throw;
	}
}

std::istream& CHTTPClientSession::receiveResponse(CHTTPResponse& response)
{
	m_pRequestStream = 0;
	if (networkException()) 
		networkException()->rethrow();

	do
	{
		response.clear();
		CHTTPHeaderInputStream his(*this);
		try
		{
			response.read(his);
		}
		catch (CException&)
		{
			close();
			if (networkException())
				networkException()->rethrow();
			else
				throw;
			throw;
		}
	} while (response.getStatus() == CHTTPResponse::HTTP_CONTINUE);

	m_mustReconnect = getKeepAlive() && !response.getKeepAlive();

	if (!m_expectResponseBody || response.getStatus() < 200 || response.getStatus() == CHTTPResponse::HTTP_NO_CONTENT || response.getStatus() == CHTTPResponse::HTTP_NOT_MODIFIED)
		m_pResponseStream = new CHTTPFixedLengthInputStream(*this, 0);
	else if (response.getChunkedTransferEncoding())
		m_pResponseStream = new CHTTPChunkedInputStream(*this);
	else if (response.hasContentLength())
#if defined(JH_HAVE_INT64)
		m_pResponseStream = new CHTTPFixedLengthInputStream(*this, response.getContentLength64());
#else
		m_pResponseStream = new CHTTPFixedLengthInputStream(*this, response.getContentLength());
#endif
	else
		m_pResponseStream = new CHTTPInputStream(*this);

	return *m_pResponseStream;
}

void CHTTPClientSession::reset()
{
	close();
}

bool CHTTPClientSession::secure() const
{
	return false;
}

int CHTTPClientSession::write(const char* buffer, std::streamsize length)
{
	try
	{
		int rc = CHTTPSession::write(buffer, length);
		m_reconnect = false;
		return rc;
	}
	catch (NetException&)
	{
		if (m_reconnect)
		{
			close();
			reconnect();
			int rc = CHTTPSession::write(buffer, length);
			m_reconnect = false;
			return rc;
		}
		else throw;
	}
}

void CHTTPClientSession::reconnect()
{
	if (m_proxyConfig.host.empty() || bypassProxy())
	{
		CSocketAddress addr(m_host, m_port);
		connect(addr);
	}
	else
	{
		CSocketAddress addr(m_proxyConfig.host, m_proxyConfig.port);
		connect(addr);
	}
}

std::string CHTTPClientSession::proxyRequestPrefix() const
{
	std::string result("http://");
	result.append(m_host);
	result.append(":");
	CNumberFormatter::append(result, m_port);
	return result;
}

bool CHTTPClientSession::mustReconnect() const
{
	if (!m_mustReconnect)
	{
		CTimeStamp now;
		return m_keepAliveTimeout <= now - m_lastRequest;
	}
	else return true;
}

void CHTTPClientSession::proxyAuthenticate(CHTTPRequest& request)
{
	proxyAuthenticateImpl(request);
}

void CHTTPClientSession::proxyAuthenticateImpl(CHTTPRequest& request)
{
	if (!m_proxyConfig.username.empty())
	{
		CHTTPBasicCredentials creds(m_proxyConfig.username, m_proxyConfig.password);
		creds.proxyAuthenticate(request);
	}
}

CStreamSocket CHTTPClientSession::proxyConnect()
{
	ProxyConfig emptyProxyConfig;
	CHTTPClientSession proxySession(getProxyHost(), getProxyPort(), emptyProxyConfig);
	proxySession.setTimeout(getTimeout());
	std::string targetAddress(m_host);
	targetAddress.append(":");
	CNumberFormatter::append(targetAddress, m_port);
	CHTTPRequest proxyRequest(CHTTPRequest::HTTP_CONNECT, targetAddress, CHTTPMessage::HTTP_1_1);
	CHTTPResponse proxyResponse;
	proxyRequest.set("Proxy-Connection", "keep-alive");
	proxyRequest.set("Host", getHost());
	proxyAuthenticateImpl(proxyRequest);
	proxySession.setKeepAlive(true);
	proxySession.sendRequest(proxyRequest);
	proxySession.receiveResponse(proxyResponse);
	if (proxyResponse.getStatus() != CHTTPResponse::HTTP_OK)
		throw HTTPException("Cannot establish proxy connection", proxyResponse.getReason());
	return proxySession.detachSocket();
}

void CHTTPClientSession::proxyTunnel()
{
	CStreamSocket ss = proxyConnect();
	attachSocket(ss);
}

bool CHTTPClientSession::bypassProxy() const
{
	if (!m_proxyConfig.nonProxyHosts.empty())
	{
		return CRegularExpression::match(m_host, m_proxyConfig.nonProxyHosts, CRegularExpression::RE_CASELESS | CRegularExpression::RE_ANCHORED);
	}
	else return false;
}

NET_NAMESPACE_END
